-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Implementation for /exec using websocket #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for taking care of this. It is great to see progress on exec support. We just need to make sure it supports all authentication methods and also let a way to interactively communicate. More info at each individual comments.
command=exec_command, | ||
stderr=False, stdin=False, | ||
stdout=True, tty=False) | ||
print('EXEC response : %s' % resp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do better instead of printing out the output? If we know what output we expect, we can check that or even part of the output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack. let's make sure we get 3 lines back
kubernetes/client/api_client.py
Outdated
@@ -343,6 +344,13 @@ def request(self, method, url, query_params=None, headers=None, | |||
""" | |||
Makes the HTTP request using RESTClient. | |||
""" | |||
if url.endswith('/exec') and method == "GET": | |||
return ws_client.GET(self.config, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I maybe not familiar enough with exec command, but does it let interactive connection with the host? I mean can a script send something, then read the result, then decide what next command to send, etc.? In that case, should we return some kind of client allows that instead of command result?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. the "/exec" is one shot only.
kubernetes/client/ws_client.py
Outdated
|
||
|
||
class WSClient: | ||
def __init__(self, configuration, url): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does configuration used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will add code for getting the ssl configuration options shortly
kubernetes/client/ws_client.py
Outdated
on_message=self.on_message, | ||
on_error=self.on_error, | ||
on_close=self.on_close, | ||
header=[]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we send authorization token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep for sure :)
kubernetes/client/ws_client.py
Outdated
self.ws.on_open = self.on_open | ||
sslopt = None | ||
if url.startswith('wss://'): | ||
sslopt = {"cert_reqs": ssl.CERT_NONE} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use ssl cert (and client cert and key) from configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right. yes.
from six.moves.urllib.parse import quote_plus | ||
|
||
|
||
class WSClient: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest testing this (even manually) on GCE, GCP and minikube to make sure we cover a good range of authentication methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mbohlool i do not have any kind of access to GCE or GCP. so i'll rely on your help with testing these
kubernetes/e2e_test/test_client.py
Outdated
else: | ||
time.sleep(1) | ||
|
||
# exec_command = 'uptime' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove the commented out codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@dims Sorry, I missed [WIP] in the title. Please ignore my comments until you are done with this. |
50919ab
to
aec77d5
Compare
7e9adec
to
df1f320
Compare
Tested by adding a TLS proxy in front of the K8S API
|
I think I had a comment about interactiveness of this solution but I cannot find it. The solution does not let the script to interactive with the server. Like open connection, run command, get output/error, run another command, etc... Is it possible to support that? |
@@ -1007,7 +1007,7 @@ def connect_get_namespaced_pod_exec_with_http_info(self, name, namespace, **kwar | |||
auth_settings=auth_settings, | |||
callback=params.get('callback'), | |||
_return_http_data_only=params.get('_return_http_data_only'), | |||
_preload_content=params.get('_preload_content', True), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this change? I mean if we need the flag to be false, we should be able to do it in the client, where we override /exec path, right? This is the only place that can be overwritten by the code generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the _preload_content tries to coerce the response into a json. would be great to find another spot to put this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api_client.py, if you move your changes out of request
method into __call_api
itself, you have access to preload_content flag. You can even branch before calling request
to make sure the preload_content flag does not have any effect. e.g. if exec, do stuff and return, else do normal stuff of calling request and preload_content stuff. We should not change generated files as much as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i figured out a way to avoid this change, please see latest update
@dims This is awesome work! |
@chekolyn kudos to you for finding a way :) |
@mbohlool : the API definition does not allow for interactivity |
you can return an object instead of an string, right? the object can have methods to work interactively with the open connection. We do that if _preload_content flag is false (returning a connection object). Having said that, instead of checking for "/exec", do you think it maybe better to pass a parameter like preload_content? something like _upgrade_connection, or _use_websocket. Then user can decide if they want to use websocket for an api call, and also we don't need to change client if anything other than /exec uses websocket (in future). |
c787a71
to
36e95fe
Compare
@mbohlool i am happy with what we have here in this PR now. i am probably missing something about interactivity idea that you have brought up a few times. I hope we can merge this and then iterate on it. I have added a FIXME for the "/exec" check in one spot, so we can deal with that later. Hope this is good enough. |
pass | ||
|
||
|
||
WSResponse = collections.namedtuple('WSResponse', ['data']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of WSResponse. make it easier to add interactivity later (which I am fine with).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does WSResponse needs an 'status'? and 'reason' in case of an error?
stderr=False, stdin=False, | ||
stdout=True, tty=False) | ||
print('EXEC response : %s' % resp) | ||
self.assertEqual(3, len(resp.splitlines())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resp is a WSResponse, right? should we check it's data
field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mbohlool the WSResponse is already unwrapped and the resp is just the string. If it were WSResponse the assert would fail :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my problem too, why the assert didn't fail. I am just trying to run it locally (just curious to see it), then I will merge. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I got the place that WSResponse is unwrapped, now I have more questions about that, e.g. why we don't have 'status' in WSResponse? rest.py checks for an status and raise an exception if status is not in OK range.
@dims OK. I am fine with this change (except one comment above) and we can merge and iterate on it later. It is in a pretty good shape. |
inspired by the POC from @chekolyn * Adds a new requirement on websocket-client * Add a new class WSClient that uses WebSocketApp from the websocket-client. * Make sure we pass Authorization header * Make sure we honor the SSL settings in configuration * Some of the code will get overwritten when we generate fresh classes from swagger definition. To remind us added a e2e test so we don't lose the changes * Added a new configuration option to enable/disable failures when hostnames in certificates don't match Fixes kubernetes-client#58
36e95fe
to
066bba1
Compare
Codecov Report@@ Coverage Diff @@
## master #120 +/- ##
=======================================
Coverage 94.46% 94.46%
=======================================
Files 9 9
Lines 668 668
=======================================
Hits 631 631
Misses 37 37 Continue to review full report at Codecov.
|
@mbohlool : there is no rest.py in the call stack. api_client.py calls ws_client.py directly so we don't need the "status" field. |
@dims Where we unwrap WSResponse then? |
This commit reapplies PR kubernetes-client#120 that was removed by kubernetes-client#353. Below is the original commit message. inspired by the POC from @chekolyn * Adds a new requirement on websocket-client * Add a new class WSClient that uses WebSocketApp from the websocket-client. * Make sure we pass Authorization header * Make sure we honor the SSL settings in configuration * Some of the code will get overwritten when we generate fresh classes from swagger definition. To remind us added a e2e test so we don't lose the changes * Added a new configuration option to enable/disable failures when hostnames in certificates don't match Fixes kubernetes-client#58 Fixes kubernetes-client#409
This commit reapplies PR kubernetes-client#120 that was removed by kubernetes-client#353. Below is the original commit message. inspired by the POC from @chekolyn * Adds a new requirement on websocket-client * Add a new class WSClient that uses WebSocketApp from the websocket-client. * Make sure we pass Authorization header * Make sure we honor the SSL settings in configuration * Some of the code will get overwritten when we generate fresh classes from swagger definition. To remind us added a e2e test so we don't lose the changes * Added a new configuration option to enable/disable failures when hostnames in certificates don't match Fixes kubernetes-client#58 Fixes kubernetes-client#409
inspired by the POC from @chekolyn
Reference #58 (by mbohlool: Removed Fix. This PR is definitely a huge step toward the right direction, but I don't call 58 done yet.)